﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using lo = sqCore.sqLogger;
using MongoDB.Bson;
using MongoDB.Driver;

namespace sqCore
{
    /* Docs:
     * Summary
        * Task object for SqlA Server.
        * Allows cyclic scheduling of multiple jobs in one Task.
        * Allows analysis of jobs' results, comparing and reporting them.
     * Variables
        * Id - Unique Id for the Task.
        * IsActive - If false, the task will not run.
        * Name - Name for the Task (Should be unique).
        * Creator - User name of Task creator.
        * CurrentJobs - List of the current jobs that should be carried out in the next Task run.
        * LastJobs - List of the last jobs and their results(if any).
        * TaskList - Contins list of the current tasks of the server.
        * NextRunTime - The time that Task should run again.
        * ExpirationTime - The Time that the Task should expire (isActive -> false).
        * Frequency - The frequency between task's Runs.
        * SaveResultInFile - Will save the results in files?
        * FilesDirectory - Directory to results files.
        * SendMail - Will send the results by mail?
        * MailTamplate - Template for the mail params (To,From,Subject...).
        * MailDefualtText - Describe the mail results.
        * CompareToLastResults - If ture, check the SendIfNoDiff & SendOnlyTheDiff.
        * SendIfNoDiff - If false, don't send mail if the result equels to the last results.
        * SendOnlyTheDiff - If true, send mail only with the rows that not in last results.
        * SendLogs - If true, Send a periodic mail with this Task's logs. 
        * TaskFirstTime - If true, this task was never ran.
        * IsOk - If false, this task failed to run.
     */
    public class sqTask
    {
        public ObjectId Id { get; set; }
        public bool IsActive { get; set; }
        public string Name { get; set; }
        public string Creator{ get; set; }
        public DateTime CreateTime { get; set; }
        public sqJob[] CurrentJobs { get; set; }
        public sqJob[] LastJobs { get; set; }
        public DateTime LastRunTime { get; set; }
        public DateTime NextRunTime { get; set; }
        public DateTime FirstRunTime { get; set; }
        public DateTime ExpirationTime { get; set; }
        public double Frequency { get; set; }
        public bool SaveResultInFile { get; set; }        
        public bool SendMail { get; set; }
        public sqMail MailTamplate { get; set; }
        public string MailDefualtText { get; set; }
        public bool SendIfResultEmplty { get; set; }
        public bool CompareToLastResults { get; set; }
        public bool SendIfNoDiff { get; set; }
        public bool SendOnlyTheDiff { get; set; }
        public bool SendLogs { get; set; }
        public bool TaskFirstTime { get; set; }
        public bool IsOk { get; set; }

        public static string FilesDirectory = @"c:\temp";
        

        /* Docs:
         * Summary: Initialize the Task.
         */
        public sqTask(string Name, string Creator, sqJob[] Jobs, bool IsActive = true, DateTime FirstRunTime = default(DateTime), DateTime ExpirationTime = default(DateTime),
                double? Frequency = null, bool SaveResultInFile = true, bool SendMail = true, sqMail MailTamplate = null , string MailDefualtText="", bool SendIfResultEmplty = true, //TODO: //CONFIG
                bool CompareToLastResults = true, bool SendIfNoDiff = false, bool SendOnlyTheDiff = false, bool SendLogs = false)
        {
            this.IsActive = IsActive;
            this.Name = Name;
            this.Creator = Creator;
            this.CreateTime = DateTime.Now;
            this.CurrentJobs = Jobs;
            if (FirstRunTime == default(DateTime))
                FirstRunTime = DateTime.Now;
            this.FirstRunTime = FirstRunTime;
            if (ExpirationTime == default(DateTime))
                ExpirationTime = DateTime.Now.AddYears(1);
            this.ExpirationTime = ExpirationTime;
            this.LastRunTime = default(DateTime);
            this.NextRunTime = this.FirstRunTime;
            if (Frequency == null)
                Frequency = 1.0;
            this.Frequency = (double)Frequency;
            this.SaveResultInFile = SaveResultInFile;            
            this.SendMail = SendMail;
            if (MailTamplate == null)
                MailTamplate = new sqMail();
            this.MailTamplate = MailTamplate;
            this.MailDefualtText=MailDefualtText;
            this.SendIfResultEmplty = SendIfResultEmplty;
            this.CompareToLastResults = CompareToLastResults;
            this.SendIfNoDiff = SendIfNoDiff;
            this.SendOnlyTheDiff = SendOnlyTheDiff;
            this.SendLogs = SendLogs;
            this.TaskFirstTime = true;
            lo.writeLog(LogMessage: "New sqTask has been created.",TaskName:this.Name, Param: this.Creator);
        }

        /* Docs:
        * Summary: TurnOn Task.
        */
        public void turnOn()
        {
            this.IsActive = true;
            lo.writeLog(LogMessage: "The task is turned on.", TaskName: this.Name);
        }
        
        /* Docs:
         * Summary: TurnOff Task.
         */
        public void turnOff()
        {
            this.IsActive = false;
            lo.writeLog(LogMessage: "The task is turned off.", TaskName: this.Name);
        }

        /* Docs:
        * Summary: Change the Task active state, if task is on -> turnOn, if task off -> turnOn
        */
        public void changeTaskActiveState()
        {
            if (this.IsActive == false)
                turnOn();
            else
                turnOff();
        }

        /* Docs:
        * Summary: Dump the task as is to file. Using json.
        * Exceptions:
            * ex - Thrown when faild to dump to file, can happand if the file path is not exists.
        */
        public void dump(string FilePath)
        {            
            try
            {
                System.IO.File.WriteAllText(FilePath, this.ToJson());
            }
            catch (System.Exception ex)
            {
                lo.writeLog("Faild to dump the task", isError: true, TaskName: this.Name, Param: ex.ToJson());
                Console.WriteLine(ex.ToString());
            }
        }

        /* Docs:
        * Summary: Deserialize sqTask from file. Using json.
        * Exceptions:
            * ex - Thrown when faild to load, can happand if the file path is not exists, or the object json is broken.
        */
        public sqTask load(string FilePath)
        {
            try
            {
                return MongoDB.Bson.Serialization.BsonSerializer.Deserialize<sqTask>(System.IO.File.ReadAllText(@FilePath));
            }
            catch (System.Exception ex)
            {
                lo.writeLog("Faild to dump the task", isError: true, TaskName: this.Name, Param: ex.ToJson());
                Console.WriteLine(ex.ToString());
                return null;
            }
        }

        /* Docs:
        * Summary: Set the next time to run the Task.
        */
        public void setNextRunTime(DateTime time = default(DateTime))
        {
            if (time==default(DateTime))
                time=DateTime.Now;
            this.NextRunTime = time;
        }

        /* Docs:
        * Summary: Get the Task's next time to run.
        */
        public DateTime getNextRunTime()
        {
            return this.NextRunTime;
        }

        /* Docs:
        * Summary: Run the Task.
            * Execute the current jobs.
            * Save jobs' results.
            * Analyse jobs results and compare it to the last results.
            * Sends the required results
        * Variables:
            * justRun - If true, the task run without change her timing params.
        */
        public void runTask(bool justRun = false)
        {
            if (NextRunTime.ToUniversalTime() <= DateTime.Now.ToUniversalTime() || justRun)
            {
                lo.writeLog(LogMessage: "Task Start Running.", TaskName: this.Name);
                if (this.TaskFirstTime)
                    lo.writeLog(LogMessage: "Task first run.", TaskName: this.Name);
                this.executeJobs(); //Run the current jobs.
                List<object> results = this.getResults();
                for (int job = 0; job < results.Count(); ++job)
                    if (results[job].GetType() == typeof(sqQueryTableResult))
                    {
                        this.applyResultsSettings(((sqQueryTableResult)results[job]), job); //Analyse jobs results and compare it to the last results.
                        if (this.SendMail)
                        {
                            this.MailTamplate.sendThroughSMTP(); //Send the results in mail.
                            lo.writeLog(LogMessage: "Mail sent.", TaskName: this.Name, Param: this.MailTamplate.MailSubject);
                        }
                        if (this.SaveResultInFile)
                        {                            
                            ((sqQueryTableResult)results[job]).makeFile(FilesDirectory + @"\" + this.NextRunTime.ToString().Replace('/', '.').Replace(':', '-') + "." + this.Name + "." + job.ToString() + @".csv"); //Save the results in file.
                            lo.writeLog(LogMessage: "Result saved in file.", TaskName: this.Name, Param: FilesDirectory + @"\" + this.NextRunTime.ToString().Replace('/', '.').Replace(':', '-') + "." + this.Name + "." + job.ToString() + @".csv");
                        }
                    }
                this.isOk(); //Check if jobs ran successfully.
                this.renewJobs(); //Move the CurrentJobs to the LastJobs
                if (!justRun)
                    this.next(); //Change the last and the next time to run, using the frequency.
                this.TaskFirstTime = false; // Not first time anymore.
                lo.writeLog(LogMessage: "Task is successfully completed.", TaskName: this.Name);
            }
            else
                lo.writeLog(LogMessage: "Task not need to run yet.", TaskName: this.Name);
        }

        /* Docs:
        * Summary: Execute the current jobs.
        * Execptions:
            * ex - Thrown when faild to run job.
        */
        public void executeJobs()
        {
            foreach (sqJob job in this.CurrentJobs)
            {                
                try 
                {
                    job.executeJob();
                }
                catch (System.Exception ex) { lo.writeLog(LogMessage: "Error: Can't run job.", isError:true, TaskName: this.Name, Params: new List<string>{ex.ToString(), job.ToJson()}); }
            }
        }

        /* Docs:
        * Summary: Get the current jobs results.
        * Execptions:
            * ex - Thrown when faild to get job results.
        */
        public List<object> getResults()
        {
            List<object> results = new List<object>();
            foreach (sqJob job in this.CurrentJobs)
            {
                try { results.Add(job.getResult()); }
                catch (System.Exception ex) { lo.writeLog(LogMessage: "Error: Can't access the job result.", isError: true, TaskName: this.Name, Param: ex.ToString()); }
            }
            return results;
        }
        
        /* Docs:
        * Summary: Get the last jobs results.
        * Execptions:
            * ex - Thrown when faild to get job results.
        */
        public List<object> getLastResults()
        {
            List<object> results = new List<object>();
            foreach (sqJob job in this.LastJobs)
            {
                try { results.Add(job.getResult()); }
                catch (System.Exception ex) { lo.writeLog(LogMessage: "Error: Can't access the job result.", isError: true,  TaskName: this.Name, Param: ex.ToString()); }
            }
            return results;
        }

        /* Docs:
        * Summary: Analyse job results and compare it to the last results.
        * Variables:
            * result - result of specific job.
            * jobIndex - the index of the specific job in CurrentJobs.
        */
        public void applyResultsSettings(sqQueryTableResult result, int jobIndex)  //TODO: add tree type options.
        {
            if (result.isEmpty())
                lo.writeLog(LogMessage: "Query results is empty.", TaskName: this.Name);
            if (result.isNotEmpty() || this.SendIfResultEmplty) //Check if results empty. if so, continue just if the flag SendIfResultEmplty is true.
                if (this.CompareToLastResults && !this.TaskFirstTime) //Check if the flag CompareToLastResults is true and this is not the first time the task running.
                { 
                    lo.writeLog(LogMessage: "Comparring to the last results.", TaskName: this.Name);
                    sqQueryTableResult[] comperedResults = result.compareSqResult((sqQueryTableResult)this.getLastResults()[jobIndex]); //Compare to the last results. return list of 3 results -> 0: The equels rows. 1: The added rows, 2: the droped rows
                    if (comperedResults[1].isEmpty() && comperedResults[2].isEmpty())
                        lo.writeLog(LogMessage: "Query results is equle to the last results.", TaskName: this.Name);
                    if ((comperedResults[1].isNotEmpty() || comperedResults[2].isNotEmpty()) || this.SendIfNoDiff) //Cheak If all rows equels. if so, continue only if the flag SendIfNoDiff is true.
                        if (this.SendOnlyTheDiff)
                            this.MailTamplate.HTMLParams = new string[2] { comperedResults[1].makeHtmlMessage("Added resulrs"), comperedResults[2].makeHtmlMessage("Droped resulrs") }; //Create mail contains only the diff.
                        else
                            this.MailTamplate.HTMLParams = new string[3] { comperedResults[1].makeHtmlMessage("Added resulrs"), comperedResults[2].makeHtmlMessage("Droped resulrs"), comperedResults[0].makeHtmlMessage("Equels resulrs") }; //Create mail.
                }
                else
                    this.MailTamplate.HTMLParams = new string[1] { result.makeHtmlMessage(this.MailDefualtText) }; //Create mail.
        }

        /* Docs:
        * Summary: Move the CurrentJobs to the LastJobs.
        */
        public void renewJobs()
        {
            try
            {
                this.LastJobs = new sqJob[this.CurrentJobs.Count()];
                for (int job = 0; job < this.CurrentJobs.Count(); ++job)
                {
                    this.LastJobs[job] = this.CurrentJobs[job].copy();
                    this.CurrentJobs[job] = this.CurrentJobs[job].renew();
                }
            }
            catch (System.Exception ex) { lo.writeLog(LogMessage: "Error: Failed to renew jobs.", TaskName: this.Name, Param: ex.ToString()); }
        }

        /* Docs:
        * Summary:  Change the last and the next time to run, using the frequency.
        * Variables:
            * DaysToAdd[Double] - Add to the NextRunTime.
            * IsRun - If false, the last run time will not change.
        * Execptions:
            * ex - Thrown when faild change the next time to run.
        */
        public void next(double? DaysToAdd = null,bool isRun = true)
        {
            try
            {
                if (DaysToAdd == null)
                    DaysToAdd = this.Frequency;
                    this.NextRunTime = this.NextRunTime.AddDays((double)DaysToAdd);
                    if (isRun)
                    {
                        this.LastRunTime = DateTime.Now;
                        lo.writeLog(LogMessage: "Task next run time sets.", TaskName: this.Name, Param: this.NextRunTime.ToLocalTime().ToString());
                    }
            }
            catch (System.Exception ex) { lo.writeLog(LogMessage: "Failed set task new timing.",isError:true, TaskName: this.Name, Param: ex.ToString()); }
        }

        /* Docs:
        * Summary:  Change the last and the next time to run, using the frequency.
        * Variables:
            * DaysToAdd[Double] - Subtract from the NextRunTime.
            * IsRun - If false, the last run time will not change.
        * Execptions:
            * ex - Thrown when faild change the next time to run.
        */
        public void back(double? DaysToAdd = null, bool isRun = true)
        {
            try
            {
                if (DaysToAdd == null)
                    DaysToAdd = (this.Frequency)*-1;
                this.NextRunTime = this.NextRunTime.AddDays((double)DaysToAdd);
                if (isRun)
                {
                    this.LastRunTime = DateTime.Now;
                    lo.writeLog(LogMessage: "Task next run time sets.", TaskName: this.Name, Param: this.NextRunTime.ToLocalTime().ToString());
                }
            }
           catch (System.Exception ex) { lo.writeLog(LogMessage: "Failed set task new timing.",isError:true, TaskName: this.Name, Param: ex.ToString()); }
        }
        
        /* Docs:
        * Summary:  Change the next time to run to most close time to now, using frequency.
        * Variables:
            * until[enum:after,before] - The mose close time before now, or after now.            
        */
        public void nextToNow(sqHelpper.NextType until)
        {
            DateTime now = DateTime.Now;
            bool isMore = true;
            bool isLess = true;            
            while (isMore || isLess)
            {
                if (this.NextRunTime >= now)
                {
                    isLess = false;
                    this.back(isRun: false);
                }

                if (this.NextRunTime <= now)
                {
                    isMore = false;
                    this.next(isRun: false);
                }
            }
            if (until == sqHelpper.NextType.before)
                this.back(isRun: false);
        }

        /* Docs:
        * Summary: Cheak if all the current jobs ran successfully.  
        */
        public void isOk()
        {
            bool isok = true;
            foreach (sqJob job in CurrentJobs)
                if (!job.IsDone)
                    isok = false;
            this.IsOk = isok;
        }


        /* Docs:
        * Summary: Check if two sqTask is equals, using the task's ids.
        */
        public override bool Equals(object t)
        {
            if (t.GetType() == typeof(sqTask))
 	            return (Id == ((sqTask)t).Id);
            return false;
        }

    } //End of sqTask
}
